/***************************************************************************
 *   Copyright (C) 2007 by Matvey Kozhev                                   *
 *   sikon@lucidfox.org                                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include "icontheme.h"

#include <QProcess>
#include <QRegExp>
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <QSettings>
#include <limits>

static const int extCount = 3;
static const char *exts[] = { "png", "svg", "xpm" };

IconTheme::IconTheme(const QString& themeName)
        : QObject(0)
{
    if (!themeName.isEmpty())
        loadTheme(themeName);
}


IconTheme::~IconTheme()
{
}


/*!
    \fn IconTheme::loadDefaultTheme(IconTheme::DefaultTheme theme)
 */
void IconTheme::loadDefaultTheme(IconTheme::DefaultTheme theme)
{
    if (theme == DefaultThemeAny)
    {
        loadDefaultTheme(DefaultThemeKDE);

        if (_themeName.isEmpty())
        {
            loadDefaultTheme(DefaultThemeGNOME);
        }
    }
    else if (theme == DefaultThemeKDE)
    {
        _themeName.clear();
        _envMap.reload();
        QDir kdehome(_envMap.at("KDEHOME", QDir::home().absoluteFilePath(".kde")));

        if (kdehome.exists())
        {
            QString config = kdehome.absoluteFilePath("share/config/kdeglobals");

            if (QFile::exists(config))
            {
                QSettings settings(config, QSettings::IniFormat);
                _themeName = settings.value("Icons/Theme").toString();
            }
        }

        reload();
    }
    else if (theme == DefaultThemeGNOME)
    {
        // Attempt to fetch the setting from gconf
        _themeName.clear();

        QProcess process;
        process.start("gconftool-2 -g /desktop/gnome/interface/icon_theme", QIODevice::ReadOnly);

        if (process.waitForStarted())
        {
            QTextStream stream(&process);

            while (process.waitForReadyRead())
                _themeName += stream.readAll();

            _themeName = _themeName.trimmed();
            process.close();
        }
        else
        {
            // Attempt to read gtkrc
            QFile file(QDir::home().absoluteFilePath(".gtkrc-2.0"));

            if (file.exists())
            {
                file.open(QIODevice::ReadOnly);
                QTextStream stream(&file);
                QRegExp exp("^\\s*gtk-icon-theme-name\\s*=(.*)");
                QString str;

                while (!(str = stream.readLine()).isEmpty())
                {
                    if (exp.indexIn(str) != -1)
                    {
                        _themeName = exp.cap(1).trimmed();

                        if ((_themeName.startsWith('"') && _themeName.endsWith('"')) ||
                                (_themeName.startsWith('\'') && _themeName.endsWith('\'')))
                        {
                            _themeName = _themeName.mid(1, _themeName.length() - 2).trimmed();
                        }
                    }
                }
            }
        }

        reload();
    }
}


/*!
    \fn IconTheme::loadTheme(const QString& theme);
 */
void IconTheme::loadTheme(const QString& theme)
{
    _themeName = theme;
    reload();
}

/*!
    \fn IconTheme::getIconPath(const QString& name, unsigned size)
 */
QString IconTheme::getIconPath(const QString& name, unsigned size) const
{
    QString key = QString(size) + ' ' + name;

    if (_cache.contains(key))
        return _cache[key];

    QString filename = findIcon(name, size);

    if (!filename.isEmpty())
        const_cast<IconTheme *>(this)->_cache.insert(key, filename);

    return filename;
}


/*!
    \fn IconTheme::reload()
 */
void IconTheme::reload()
{
    _envMap.reload();
    _cache.clear();
    _list.clear();
    _basedirs.clear();

    QString basedir = QDir::home().absoluteFilePath(".icons");

    if (QDir(basedir).exists())
        _basedirs.append(basedir);

    QStringList datadirs = _envMap.at("XDG_DATA_DIRS").split(':');

    for (int i = 0; i < datadirs.size(); i++)
    {
        if (!datadirs.at(i).isEmpty())
        {
            basedir = QDir(datadirs.at(i)).absoluteFilePath("icons");

            if (QDir(basedir).exists())
                _basedirs.append(basedir);
        }
    }

    basedir = "/usr/share/pixmaps";
    if (QDir(basedir).exists())
        _basedirs.append(basedir);

    addThemeData(_themeName);
    addThemeData("hicolor");
}


/*!
    \fn IconTheme::addThemeData(const QString& name)
 */
void IconTheme::addThemeData(const QString& name)
{
    if (name.isEmpty())
        return;

    // Check if the theme has already been added
    for (QList<IconThemeData>::iterator it = _list.begin(); it != _list.end(); ++it)
        if (it->name == name)
            return;
    
    bool exists = false;
    IconThemeData data;
    data.name = name;
    QDir dir;

    for (int i = 0; i < _basedirs.size(); i++)
    {
        data.dir = QDir(_basedirs.at(i)).absoluteFilePath(name);
        dir = QDir(data.dir);

        if (dir.exists() && QFile::exists(dir.absoluteFilePath("index.theme")))
        {
            exists = true;
            break;
        }
    }

    if (!exists)
        return;

    QSettings settings(dir.absoluteFilePath("index.theme"), QSettings::IniFormat);
    settings.beginGroup("Icon Theme");
    data.parents = settings.value("Inherits").toStringList();
    QStringList subdirList = settings.value("Directories").toStringList();
    settings.endGroup();

    data.subdirs.resize(subdirList.size());
    
    for (int i = 0; i < subdirList.size(); i++)
    {
        // The defaults are dictated by the FDO specification
        IconDirData& dirdata = data.subdirs[i];

        settings.beginGroup(subdirList.at(i));
        dirdata.path = subdirList.at(i);
        dirdata.size = settings.value("Size").toUInt();
        dirdata.maxsize = settings.value("MaxSize", dirdata.size).toUInt();
        dirdata.minsize = settings.value("MinSize", dirdata.size).toUInt();
        dirdata.threshold = settings.value("Threshold", 2).toUInt();
        QString type = settings.value("Type", "Threshold").toString();
        settings.endGroup();

        if (type == "Fixed")
            dirdata.type = IconDirData::Fixed;
        else if (type == "Scalable")
            dirdata.type = IconDirData::Scalable;
        else
            dirdata.type = IconDirData::Threshold;
    }

    _list.append(data);

    for(int i = 0; i < data.parents.size(); i++)
        addThemeData(data.parents.at(i));
}


/*!
    \fn IconTheme::findIcon(const QString& name, unsigned size)
 */
QString IconTheme::findIcon(const QString& name, unsigned size) const
{
    QString filename;

    for (QList<IconThemeData>::const_iterator it = _list.begin(); it != _list.end(); ++it)
    {
        filename = lookupIcon(name, size, *it);

        if (filename != QString())
            return filename;
    }

    return lookupFallbackIcon(name);
}


/*!
    \fn IconTheme::lookupIcon(const QString& name, unsigned size, const IconThemeData &data);
 */
QString IconTheme::lookupIcon(const QString& name, unsigned size, const IconThemeData &data) const
{
    // Look for an exact size match first, per specification
    // (Can this be optimized? After all, we later seek the minimum distance
    // anyway, so why not unite the two loops?)
    for (int i = 0; i < data.subdirs.size(); i++)
    {
        const IconDirData& dirdata = data.subdirs[i];

        if (dirMatchesSize(dirdata, size))
        {
            for (int j = 0; j < _basedirs.size(); j++)
            {
                for (int k = 0; k < extCount; k++)
                {
                    QString filename = QDir(_basedirs.at(j)).absoluteFilePath
                            (data.name + '/' + dirdata.path + '/' + name + '.' + exts[k]);

                    if (QFile::exists(filename))
                        return filename;
                }
            }
        }
    }

    unsigned mindist = std::numeric_limits<unsigned>::max();
    QString closestFilename;
    
    for (int i = 0; i < data.subdirs.size(); i++)
    {
        const IconDirData& dirdata = data.subdirs[i];
        unsigned distance = dirSizeDistance(dirdata, size);
        
        if(distance >= mindist)
            continue;
        
        // Here we look for the first match in the given subdir.
        // If found, we decrease the mindist, immediately exit
        // the inner loops and move to the next subdir
        for (int j = 0; j < _basedirs.size(); j++)
        {
            bool found = false;
            
            for (int k = 0; k < extCount; k++)
            {
                QString filename = QDir(_basedirs.at(j)).absoluteFilePath
                        (data.name + '/' + dirdata.path + '/' + name + '.' + exts[k]);

                if (QFile::exists(filename))
                {
                    closestFilename = filename;
                    mindist = distance;
                    found = true;
                    break;
                }
            }
            
            if(found)
                break;
        }
        
        // mindist can't be below 1, so if we reached that,
        // it's pointless to look further
        if(mindist == 1)
            break;
    }
    
    return closestFilename;
}


/*!
    \fn IconTheme::lookupFallbackIcon(const QString& name)
 */
QString IconTheme::lookupFallbackIcon(const QString& name) const
{
    for (int i = 0; i < _basedirs.size(); i++)
    {
        QDir dir(_basedirs.at(i));

        for (int j = 0; j < extCount; j++)
        {
            QString fullname = dir.absoluteFilePath(name + '.' + exts[j]);

            if (QFile::exists(fullname))
                return fullname;
        }
    }

    return QString();
}

#ifdef QT_GUI_LIB
/*!
    \fn IconTheme::getPixmap(const QString& name, int size)
 */
QPixmap IconTheme::getPixmap(const QString& name, int size) const
{
    QPixmap pm(getIconPath(name));
    
    if(pm.size() != QSize(size, size))
        pm = pm.scaled(QSize(size, size));
    
    return pm;
}
#endif


/*!
    \fn IconTheme::dirMatchesSize(const IconDirData &dir, unsigned size)
 */
bool IconTheme::dirMatchesSize(const IconDirData &dir, unsigned size) const
{
    switch (dir.type)
    {
    case IconDirData::Fixed:
        return size == dir.size;
    case IconDirData::Scalable:
        return (size >= dir.minsize) && (size <= dir.maxsize);
    case IconDirData::Threshold:
        return (size >= dir.size - dir.threshold) && (size <= dir.size + dir.threshold);
    }

    // We shouldn't really get here - if we do, it's probably a bug
    return false;
}


/*!
    \fn IconTheme::dirSizeDistance(const IconDirData& dir, unsigned size)
 */
unsigned IconTheme::dirSizeDistance(const IconDirData& dir, unsigned size) const
{
    switch (dir.type)
    {
        case IconDirData::Fixed:
            return abs(dir.size - size);
        case IconDirData::Scalable:
            if(size < dir.minsize)
                return dir.minsize - size;
            if(size > dir.maxsize)
                return size - dir.maxsize;
            return 0;
        case IconDirData::Threshold:
            if(size < dir.size - dir.threshold)
                return dir.size - dir.threshold - size;
            if(size > dir.size + dir.threshold)
                return size - dir.size - dir.threshold;
            return 0;
    }
    
    // We shouldn't really get here - if we do, it's probably a bug
    return 0;
}
